﻿using System;
using SLRT.business.api;
using System.Threading;
using System.Drawing;
using Emgu.CV;
using SLRT.dao.data;
using System.Collections.Generic;

using System.Diagnostics;

namespace SLRT.business.src
{
    class Brain : IBrain
    {
        private static readonly double MINIMUM_RATE_NEEDED_TO_MATCH = 0.2;        
        private int handCutX1;
        private int handCutY1;
        private int handCutX2;
        private int handCutY2;
        private readonly Object lockObject = new Object();
        private CaptureModel _captureModel;
        private Boolean _capturedImageChanged;
        private Bitmap processedImage;
        
        private Boolean algorithmRunning;
        private int _result;

        #region Properties

        public int Result
        {
            get { return _result; }
            set { _result = value; }
        }

        public Boolean checkAlgorithmRunning()
        {
            return this.algorithmRunning;
        }

        public void startAlgorithmRunning()
        {
            this.algorithmRunning = true;
        }

        /**
         * Commands this Brain to suspend 
         */
        public void stopAlgorithmRunning()
        {
            this.algorithmRunning = false;
        }


        public Boolean CapturedImageChanged
        {
            get { return _capturedImageChanged; }
            set { _capturedImageChanged = value; }
        }

        #endregion

        public Brain(int handCutX1, int handCutY1, int handCutX2, int handCutY2)
        {
            this.handCutX1 = handCutX1;
            this.handCutX2 = handCutX2;
            this.handCutY1 = handCutY1;
            this.handCutY2 = handCutY2;
            this._captureModel = CaptureModel.Instance;
            this._capturedImageChanged = false;
            this.algorithmRunning = false;
            this._result = -1;
        }


        public void ThreadRun()
        {            
            int currentResult = this._result;

            Stopwatch mainStopwatch = new Stopwatch();
            Stopwatch stopwatch01 = new Stopwatch();
            Stopwatch stopwatch02 = new Stopwatch();
            Stopwatch stopwatch03 = new Stopwatch();
            Stopwatch stopwatch04 = new Stopwatch();
            Stopwatch stopwatch05 = new Stopwatch();
            Stopwatch stopwatch06 = new Stopwatch();
            Stopwatch stopwatch07 = new Stopwatch();
            Stopwatch stopwatch08 = new Stopwatch();
            int nMeasureLoops = 0;

            mainStopwatch.Start();

            while (true)
            {
                
                if (mainStopwatch.ElapsedMilliseconds > 5000)
                {                    
                    Console.WriteLine("Brain: After {0}ms and {1} measuring loops", mainStopwatch.ElapsedMilliseconds, nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch01 average is {0}ms", (double) stopwatch01.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch02 average is {0}ms", (double) stopwatch02.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch03 average is {0}ms", (double) stopwatch03.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch04 average is {0}ms", (double) stopwatch04.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch05 average is {0}ms", (double) stopwatch05.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch06 average is {0}ms", (double) stopwatch06.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch07 average is {0}ms", (double) stopwatch07.ElapsedMilliseconds / nMeasureLoops);
                    Console.WriteLine("Brain: stopwatch08 average is {0}ms", (double) stopwatch08.ElapsedMilliseconds / nMeasureLoops);

                    nMeasureLoops = 0;
                    mainStopwatch.Reset();
                    mainStopwatch.Start();
                    stopwatch01.Reset();
                    stopwatch02.Reset();
                    stopwatch03.Reset();
                    stopwatch04.Reset();
                    stopwatch05.Reset();
                    stopwatch06.Reset();
                    stopwatch07.Reset();
                    stopwatch08.Reset();
                }
                nMeasureLoops++;

                //classifyTest();
                Thread.Sleep(100);
                
                if (algorithmRunning /*&& this._capturedImageChanged*/)
                {                   
                    lock (this.lockObject)
                    {
                        this._capturedImageChanged = false;
                    }

                    /*
                        * Processing and information aquiring part.
                        */
                    IImage capturedIm = CaptureModel.Instance.Iimage;                        
                    //CaptureModel.Instance.ProcessedImage = capturedIm.Bitmap;                        
                    double ar = (double) (handCutX2 - handCutX1) / (handCutY2 - handCutY1);
                    int camImWidth, camImHeight;
                    int cutX1 = handCutX1;
                    int cutX2 = handCutX2;
                    int cutY1 = handCutY1;
                    int cutY2 = handCutY2;
                    if (ar < DBImage.DB_IMAGE_ASPECT_RATIO)
                    {
                        camImWidth = (int)Math.Round(DBImage.DB_IMAGE_HEIGHT * ar);
                        camImHeight = DBImage.DB_IMAGE_HEIGHT;
                        double cutHeight = (handCutY2 - handCutY1 - (handCutX2 - handCutX1) / DBImage.DB_IMAGE_ASPECT_RATIO) / 2;
                        cutY1 = (int)(handCutY1 + cutHeight);
                        cutY2 = (int)(handCutY2 - cutHeight);
                    }
                    else
                    {
                        camImWidth = DBImage.DB_IMAGE_WIDTH;
                        camImHeight = (int)Math.Round(DBImage.DB_IMAGE_WIDTH / ar);
                        double cutWidth = (handCutX2 - handCutX1 - (handCutY2 - handCutY1) * DBImage.DB_IMAGE_ASPECT_RATIO) / 2;
                        cutX1 = (int)(handCutX1 + cutWidth);
                        cutX2 = (int)(handCutX2 - cutWidth);
                    }
                    stopwatch01.Start();
                        byte[] byteIm = ImageAlgorithms.BmpToBytes_Unsafe(capturedIm.Bitmap);
                    stopwatch01.Stop();
                    //CaptureModel.Instance.ProcessedImage = ImageAlgorithms.byteIm2Bitmap2(byteIm, capturedIm.Bitmap.Width, capturedIm.Bitmap.Height);

                    stopwatch02.Start();
                        int[,] cutResizedGrayIntIm = ImageAlgorithms.bitmapToResizedGreyscaleMatrix(byteIm, capturedIm.Bitmap.Width, capturedIm.Bitmap.Height, cutX1, cutY1, cutX2, cutY2, DBImage.DB_IMAGE_WIDTH, DBImage.DB_IMAGE_HEIGHT);
                    stopwatch02.Stop();                    
                    //CaptureModel.Instance.ProcessedImage = ImageAlgorithms.grayIntIm2BuffIm(cutResizedGrayIntIm);

                    stopwatch03.Start();
                        int[,] contourIntIm = new int[DBImage.DB_IMAGE_HEIGHT, DBImage.DB_IMAGE_WIDTH];
                        int[] histogram = new int[255];                           
                        ImageAlgorithms.toContour(cutResizedGrayIntIm, DBImage.CONTOUR_POWER, ref contourIntIm, ref histogram);                        
                        int threshold = ImageAlgorithms.computeNecessaryThreshold(DBImage.WHITE_PROPORTION, histogram, camImWidth * camImHeight);                        
                        bool[,] camBoolIm = ImageAlgorithms.grayIntIm2BoolIm(contourIntIm, threshold);
                    stopwatch03.Stop();                    
                    //CaptureModel.Instance.ProcessedImage = ImageAlgorithms.boolIm2BuffIm(camBoolIm, 0, 255);

                    /*
                    stopwatch04.Start();
                    Shape greatestShape = ImageAlgorithms.findGreatestShape(camBoolIm);
                    stopwatch04.Stop();   
                    /* for testing: */
                    //CaptureModel.Instance.ProcessedImage = ImageAlgorithms.boolIm2BuffIm(ImageAlgorithms.shape2BoolIm(greatestShape, 320, 240), 0, 255);

                    stopwatch05.Start();
                    List<Shape> allShapes = ImageAlgorithms.extractAllShapes(camBoolIm);
                    Shape largestShapes = ImageAlgorithms.MergeShapes(allShapes, 6);                    
                    stopwatch05.Stop();   
                    /* for testing: */
                    //CaptureModel.Instance.ProcessedImage = ImageAlgorithms.boolIm2BuffIm(ImageAlgorithms.shape2BoolIm(largestShapes, 320, 240), 0, 255);
                    if (largestShapes == null)
                    {
                        byte[] kk = ImageAlgorithms.boolIm2byteIm(camBoolIm, Color.Blue, Color.White);
                        CaptureModel.Instance.ProcessedImage = ImageAlgorithms.byteIm2Bitmap2(kk, 320, 240);
                        continue;
                    }

                    stopwatch06.Start();
                    Point? leftMostPoint = largestShapes.LeftMostPoint;
                    Point? rightMostPoint = largestShapes.RightMostPoint;
                    Point? bottomMostPoint = largestShapes.BottomMostPoint;
                    Point? topMostPoint = largestShapes.TopMostPoint;
                    
                    Shape leftShape = ImageAlgorithms.reduceShapeHVAreaLimit(largestShapes, leftMostPoint, DBImage.HV_THINNESS, true, DBImage.HV_AREA_LIMIT);
                    Shape rightShape = ImageAlgorithms.reduceShapeHVAreaLimit(largestShapes, rightMostPoint, DBImage.HV_THINNESS, true, DBImage.HV_AREA_LIMIT);
                    Shape topShape = ImageAlgorithms.reduceShapeHVAreaLimit(largestShapes, topMostPoint, DBImage.HV_THINNESS, false, DBImage.HV_AREA_LIMIT);
                    Shape bottomShape = ImageAlgorithms.reduceShapeHVAreaLimit(largestShapes, bottomMostPoint, DBImage.HV_THINNESS, false, DBImage.HV_AREA_LIMIT);
                    
                    int camLeftShapeCenter = leftShape.getCenter().Value.X;
                    int camTopShapeCenter = topShape.getCenter().Value.Y;
                    int camShapeWidth = rightShape.getCenter().Value.X - camLeftShapeCenter;
                    int camShapeHeight = bottomShape.getCenter().Value.Y - camTopShapeCenter;
                    stopwatch06.Stop();
                                                
                    bool[,] largestShapesBoolIm = ImageAlgorithms.shape2BoolIm(largestShapes, 320, 240);
                    bool[,] transZoomedBoolIm = null;
                    
                    /* Comparison part.
                     * Similar to that sucky algorithm Iulia showed me.
                     * (The algorithm is sucky not Iulia)
                     * BTW talkin'bout Iulia M., not Iulia P. cause Iulia P. _is_ forsure.
                     */
                    List<Letter> letters = CaptureModel.Instance.Letters;

                    double bestMatch = 0;
                    int bestMatchedImageIndex = 0;
                    int bestMatchedLetterIndex = 0;   
                    int letterIndex = 0;

                    letters.ForEach(letter =>
                    {
                        
                    //Letter letter = letters[0];                                                                                               
                        List<DBImage> dbIms = letter.DBImages;
                        int imIndex = 0;

                        dbIms.ForEach(dbIm =>
                        {
                       
                        //DBImage dbIm = dbIms[0];
                            bool[,] dbBoolIm = dbIm.Raster;                                                       
                            
                            int dbLeftShapeCenter = dbIm.LeftShapeCenter;
                            int dbTopShapeCenter = dbIm.TopShapeCenter;
                            int dbShapeWidth = dbIm.ShapeWidth;
                            int dbShapeHeight = dbIm.ShapeHeight;

                            stopwatch08.Start();
                            /* two dimensional, non-fixed ratio scaling */
                            transZoomedBoolIm = ImageAlgorithms.translateAndScale(largestShapesBoolIm, dbLeftShapeCenter - camLeftShapeCenter, dbTopShapeCenter - camTopShapeCenter, (double)dbShapeWidth / camShapeWidth, (double)dbShapeHeight / camShapeHeight, camLeftShapeCenter, camTopShapeCenter);
                            
                            /* two dimensional fixed x ratio scaling */
                            //bool[,] transZoomedBoolIm = ImageAlgorithms.translateAndScale(ImageAlgorithms.shape2BoolIm(largestShapes, 320, 240), dbLeftShapeCenter - camLeftShapeCenter, dbTopShapeCenter - camTopShapeCenter, (double)dbShapeWidth / camShapeWidth, (double)dbShapeWidth / camShapeWidth, camLeftShapeCenter, camTopShapeCenter);        
                            //this.processedImage = ImageAlgorithms.boolIm2BuffIm(transZoomedBoolIm, Color.BLACK.getRGB(), Color.BLUE.brighter().getRGB());                                                        
                            stopwatch08.Start();

                            /* for testing: */
                            /*
                            stopwatch07.Start();
                            byte[] debugIm = ImageAlgorithms.boolIm2byteIm(dbBoolIm, Color.Black, Color.LightGray);
                            debugIm = ImageAlgorithms.boolImOverByteIm(camBoolIm, debugIm, Color.DarkGray);
                            debugIm = ImageAlgorithms.boolImHighlightOverByteIm(transZoomedBoolIm, debugIm, Color.White, Color.FromArgb(0, 255, 0));
                            debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(leftShape, 320, 240), debugIm, Color.Blue);
                            debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(rightShape, 320, 240), debugIm, Color.Red);
                            debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(topShape, 320, 240), debugIm, Color.Yellow);
                            debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(bottomShape, 320, 240), debugIm, Color.Magenta);
                            CaptureModel.Instance.ProcessedImage = ImageAlgorithms.byteIm2Bitmap2(debugIm, 320, 240);
                            stopwatch07.Stop();
                            */

                            //double match = ImageAlgorithms.compareTwoBooleanImages(camBoolIm, dbBoolIm);
                            double match = ImageAlgorithms.compareTwoBooleanImages(ImageAlgorithms.shape2BoolIm(largestShapes, 320, 240), dbBoolIm);
                            
                            if (match >= Brain.MINIMUM_RATE_NEEDED_TO_MATCH)
                            {
                                if (match > bestMatch)
                                {
                                    bestMatch = match;
                                    bestMatchedImageIndex = imIndex;
                                    bestMatchedLetterIndex = letterIndex;                                    
                                }                               
                            }

                            imIndex++;                         
                        });                        
                        letterIndex++;
                    });

                    stopwatch07.Start();
                    byte[] debugIm = ImageAlgorithms.boolIm2byteIm(letters[bestMatchedLetterIndex].DBImages[bestMatchedImageIndex].Raster, Color.Black, Color.DarkRed);
                    debugIm = ImageAlgorithms.boolImOverByteIm(camBoolIm, debugIm, Color.DarkGray);
                    //debugIm = ImageAlgorithms.boolImHighlightOverByteIm(transZoomedBoolIm, debugIm, Color.White, Color.FromArgb(0, 255, 0));
                    debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(leftShape, 320, 240), debugIm, Color.Blue);
                    debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(rightShape, 320, 240), debugIm, Color.Red);
                    debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(topShape, 320, 240), debugIm, Color.Yellow);
                    debugIm = ImageAlgorithms.boolImOverByteIm(ImageAlgorithms.shape2BoolIm(bottomShape, 320, 240), debugIm, Color.Magenta);
                    CaptureModel.Instance.ProcessedImage = ImageAlgorithms.byteIm2Bitmap2(debugIm, 320, 240);
                    stopwatch07.Stop();
                    //Console.WriteLine("Brain: compared with "+ letterIndex + " letters, maxMatchedDouble = " + maxMatchedDouble + ", maxMatchedIndex = " + maxMatchedIndex + ".\n");

                    /* Evaluating the comparison. */
                    
                    if (bestMatch > 0)
                    {
                        currentResult = bestMatchedLetterIndex;
                    }
                    else
                    {
                        currentResult = -1;
                    }
                                         
                }

                if (currentResult != this.Result)
                {
                    this.Result = currentResult;
                    Console.WriteLine("Brain: new result = " + Result + "\n");
                    CaptureModel.Instance.BrainResultReceived = true;
                }
            }
        }
        


        public static void classifyTest()
        {
            try
            {
                Console.WriteLine("Hello Java, from C#!");
                weka.core.Instances insts = new weka.core.Instances(new java.io.FileReader("D:/iris.arff"));
                insts.setClassIndex(insts.numAttributes() - 1);

                weka.classifiers.Classifier cl = new weka.classifiers.trees.J48();
                Console.WriteLine("Performing " + 33 + "% split evaluation.");

                //randomize the order of the instances in the dataset.
                weka.filters.Filter myRandom = new weka.filters.unsupervised.instance.Randomize();
                myRandom.setInputFormat(insts);
                insts = weka.filters.Filter.useFilter(insts, myRandom);

                int trainSize = insts.numInstances() * 33 / 100;
                int testSize = insts.numInstances() - trainSize;
                weka.core.Instances train = new weka.core.Instances(insts, 0, trainSize);

                cl.buildClassifier(train);
                int numCorrect = 0;
                for (int i = trainSize; i < insts.numInstances(); i++)
                {
                    weka.core.Instance currentInst = insts.instance(i);
                    double predictedClass = cl.classifyInstance(currentInst);
                    if (predictedClass == insts.instance(i).classValue())
                        numCorrect++;
                }
                Console.WriteLine(numCorrect + " out of " + testSize + " correct (" +
                           (double)((double)numCorrect / (double)testSize * 100.0) + "%)");
            }
            catch (Exception ex)
            {
                
            }
        }
    }
}